
package vu.cs.heuristieken;

import java.util.List;
import java.util.LinkedList;
import java.util.ListIterator;
import vu.cs.heuristieken.gui.GuiWindow;

public class Searcher {
    public Searcher() 
        throws CloneNotSupportedException  {

        int nHouses            = 120;
        int nEengezinsWoningen = (int) Math.floor(nHouses * 0.60);
        int nBungalows         = (int) Math.floor(nHouses * 0.25);
        int nMaisons           = (int) Math.floor(nHouses * 0.15);

        HouseList<House> houses = new HouseList<House>();
        for(int i = 0; nEengezinsWoningen > i; i++) {
            houses.add(new EengezinsWoning());
        }
        for(int i = 0; nBungalows > i; i++) {
            houses.add(new Bungalow());
        }
        List<Field> fields                    = this._getSimplifiedFields(new Size(320, 240), houses);
        List<List<List<Placement>>> subFields = this._findEmptySubfields(fields);
        GuiWindow window                      = new GuiWindow();
        window.showFields(fields, subFields);
    }

    private List<List<List<Placement>>> _findEmptySubfields(List<Field> fields) 
        throws CloneNotSupportedException {
        List<List<List<Placement>>> result = new LinkedList<List<List<Placement>>>();
        for(Field field : fields) {
            result.add(this.__findEmptySubfields(field));
        }
        return result;
    }

    private List<List<Placement>> __findEmptySubfields(Field field) 
        throws CloneNotSupportedException {
        List<List<Placement>> result = new LinkedList<List<Placement>>();
        result.add(this.__findEmptySubfields(field, -1, -1));
        result.add(this.__findEmptySubfields(field, -1,  1));
        result.add(this.__findEmptySubfields(field,  1, -1));
        result.add(this.__findEmptySubfields(field,  1,  1));
        return result;
    }

    // TODO: Yuck! Clean this shit up!
    private List<Placement> __findEmptySubfields(Field field, int horizontalDirection, int verticalDirection) 
        throws CloneNotSupportedException {
        List<Placement> result = new LinkedList<Placement>();
        Field workField        = field.clone();

        int startX, startY, endX, endY, tooFarX, tooFarY;
        if(0 < horizontalDirection) {
            startX  = 0;
            endX    = workField.getWidth() - 1;
            tooFarX = workField.getWidth();
        }
        else {
            startX  = workField.getWidth() - 1;
            endX    = 0;
            tooFarX = -1;
        }

        if(0 < verticalDirection) {
            startY  = 0;
            endY    = workField.getDepth() - 1;
            tooFarY = workField.getDepth();
        }
        else {
            startY  = workField.getDepth() - 1;
            endY    = 0;
            tooFarY = -1;
        }

        for(int x = startX; tooFarX != x; x += horizontalDirection) {
            for(int y = startY; tooFarY != y; y += verticalDirection) {
                if(CellType.CELLTYPE_EMPTY == workField.getCellType(x, y)) {
                    System.out.println("(" + x + "," + y + ") is empty.");
                    int _startX = x;
                    int _startY = y;
                    int _endX   = _startX;
                    while((endX != _endX) && (CellType.CELLTYPE_EMPTY == workField.getCellType(_endX + horizontalDirection, _startY))) {
                        _endX += horizontalDirection;
                    }

                    int _endY    = _startY;
                    boolean cont = true;
                    while(cont) {
                        for(int _x = _startX; (_endX + horizontalDirection) != _x; _x += horizontalDirection) {
                            if((_endY == endY) || (CellType.CELLTYPE_EMPTY != workField.getCellType(_x, _endY + verticalDirection))) {
                                cont = false;
                                break;
                            }
                        }
                        if(cont) {
                            _endY += verticalDirection;
                        }
                    }
                    
                    int __startX, __startY, __endX, __endY;
                    if(0 < horizontalDirection) {
                        __startX = _startX;
                        __endX   = _endX;
                    }
                    else {
                        __startX = _endX;
                        __endX   = _startX;
                    }

                    if(0 < verticalDirection) {
                        __startY = _startY;
                        __endY   = _endY;
                    }
                    else {
                        __startY = _endY;
                        __endY   = _startY;
                    }

                    while((0 != __startX) && (this.__testAreaForAvailability(workField, __startX - 1, __startY, __endX, __endY))) {
                        __startX--;
                    }
                    while((0 != __startY) && (this.__testAreaForAvailability(workField, __startX, __startY - 1, __endX, __endY))) {
                        __startY--;
                    }
                    while(((workField.getWidth() - 1) != __endX) && (this.__testAreaForAvailability(workField, __startX, __startY, __endX + 1, __endY))) {
                        __endX++;
                    }
                    while(((workField.getDepth() - 1) != __endY) && (this.__testAreaForAvailability(workField, __startX, __startY, __endX, __endY + 1))) {
                        __endY++;
                    }

                    result.add(new Placement(workField.getEmptySubfield(__startX, __startY, __endX, __endY), new Position(__startX, __startY)));
                    workField.reserveSubfield(__startX, __startY, __endX, __endY);
                }
            }
        }
        return result;
    }

    private boolean __testAreaForAvailability(Field field, int startX, int startY, int endX, int endY) {
        for(int x = startX; endX >= x; x++) {
            for(int y = startY; endY >= y; y++) {
                if((CellType.CELLTYPE_EMPTY     != field.getCellType(x, y))
                && (CellType.CELLTYPE_VRIJSTAND != field.getCellType(x, y))) {
                    return false;
                }
            }
        }
        return true;
    }

    private List<Field> _getSimplifiedFields(Size fieldSize, HouseList<House> houses) 
        throws CloneNotSupportedException {

        List<List<Placement>> validPlacements = null;
        validPlacements = this._filterValidPlacements(this._searchColumns(fieldSize, houses), houses);

        for(List<Placement> placements : validPlacements) {
            for(Placement placement : placements) {
                Position position = placement.getPosition();
            }
        }

        validPlacements = this._fillColumns(validPlacements, houses);

        List<Field> fields = new LinkedList<Field>();
        for(List<Placement> placements : validPlacements) {
            fields.add(new Field(fieldSize, placements));
        }
        return fields;
    }

    private List<List<Placement>> _fillColumns(List<List<Placement>> columnPlacements, List<House> houses) 
        throws CloneNotSupportedException {
        List<List<Placement>> result = new LinkedList<List<Placement>>();
        for(List<Placement> columns : columnPlacements) {
            result.add(this.__fillColumns(columns, houses));
        }
        return result;
    }

    private List<Placement> __fillColumns(List<Placement> columnPlacements, List<House> houses) 
        throws CloneNotSupportedException {
        List<Placement> newPlacements = new LinkedList<Placement>();
        for(Placement placement : columnPlacements) {
            newPlacements.add(placement);
        }

        while(true) {
            House currentHouse = houses.get(0);

            for(int currentColumn = 0; newPlacements.size() > currentColumn; currentColumn++) {
                Placement currentPlacement = newPlacements.get(currentColumn);
                Column column              = (Column) currentPlacement.getEntity();

                if(column.getHouseType() == currentHouse.getClass()) {
                    column.addPlacement(new Placement(column.getHouseInstance(), new Position(0, 0)));
                    houses = houses.subList(1, houses.size());
                    if(0 == houses.size()) {
                        return newPlacements;
                    }
                }
            }
        }
    }

    private List<List<Placement>> _filterValidPlacements(List<List<Placement>> placementList, HouseList<House> houses) {
        List<List<Placement>> validPlacements = new LinkedList<List<Placement>>();
        for(List<Placement> placements : placementList) {
            if(this._isValidPlacement(placements, houses)) {
                validPlacements.add(placements);
            }
        }
        return validPlacements;
    }

    private boolean _isValidPlacement(List<Placement> placements, HouseList<House> houses) {
        List<Class<?>> houseTypes = houses.getHouseTypes();
        int[] availability        = new int[houseTypes.size()];
        
        for(Placement placement : placements) {
            if(placement.getEntity() instanceof Column) {
                Column column = (Column) placement.getEntity();
                for(int i = 0; availability.length > i; i++) {
                    if(column.getHouseType() == houseTypes.get(i)) {
                        availability[i] += column.getHouseAvailability();
                        break;
                    }
                }
            }
        }

        for(int i = 0; availability.length > i; i++) {
            if(availability[i] < houses.getNHousesOfType(houseTypes.get(i))) {
                return false;
            }
        }
        return true;
    }

    private List<List<Placement>> _searchColumns(Size fieldSize, HouseList<House> houses) 
        throws CloneNotSupportedException {
        List<List<Placement>> results = new LinkedList<List<Placement>>();
        this.__searchColumns(fieldSize, houses.getUniqueHouses(), 0, 0, new LinkedList<Placement>(), results);
        return results;
    }

    private void __searchColumns(Size fieldSize, List<House> houses, int currentX, int lastVrijstand, List<Placement> currentPlacements, List<List<Placement>> results)
        throws CloneNotSupportedException {
        if(currentX >= (fieldSize.getWidth() - 1)) {
            List<Placement> newPlacements = new LinkedList<Placement>();
            for(Placement placement : currentPlacements) {
                newPlacements.add(placement.clone());
            }
            results.add(newPlacements);
        }
        else if(0 != houses.size()) {
            House currentHouse = houses.get(0);
            if(0 != currentX && currentHouse.getVrijstand() > lastVrijstand) {
                currentX += (currentHouse.getVrijstand() - lastVrijstand);
            }

            if(fieldSize.getWidth() > (currentX + currentHouse.getWidth())) {
                int columnWidth                = (0 == currentX)? currentHouse.getWidth() : currentHouse.getWidth() + currentHouse.getVrijstand();
                List<Placement> nextPlacements = new LinkedList<Placement>(currentPlacements);
                nextPlacements.add(new Placement(new Column(new Size(columnWidth, fieldSize.getDepth()),
                                                            currentHouse.clone()),
                                                 new Position(currentX, 0)));
                this.__searchColumns(fieldSize, houses, currentX + currentHouse.getWidth() + currentHouse.getVrijstand(), currentHouse.getVrijstand(), nextPlacements, results);
            }

            if(!currentHouse.isRotated() && currentHouse.isRotatable()) {
                currentHouse.rotate();
                this.__searchColumns(fieldSize, houses, currentX, lastVrijstand, currentPlacements, results);
                currentHouse.rotate();
            }
            if(1 != houses.size()) {
                this.__searchColumns(fieldSize, houses.subList(1, houses.size()), currentX, lastVrijstand, currentPlacements, results);
            }
        } 
    }
}
